package com.mybatis.utils;

import com.mybatis.anno.Select;
import com.mybatis.cfg.Configuration;
import com.mybatis.cfg.Mapper;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XmlUtils {

    public static Configuration loadXMLConfiguration(InputStream inputStream) throws Exception {

        Configuration configuration = new Configuration();
        SAXReader reader = new SAXReader();
        Element rootElement = reader.read(inputStream).getRootElement();

        loadMainXML(rootElement, configuration);
        loadMapperXML(rootElement, configuration);


        return configuration;
    }



    private static Configuration loadMainXML(Element rootElement, Configuration configuration){
        Element dataSourceElement = rootElement.element("environments").element("environment").element("dataSource");
        List<Element> propertys = dataSourceElement.elements("property");
        for (Element element : propertys){

            String attributeName = element.attributeValue("name");
            String attributeValue = element.attributeValue("value");

            if("driver".equals(attributeName)){
                configuration.setDriver(attributeValue);
            }else if("url".equals(attributeName)){
                configuration.setUrl(attributeValue);
            }else if("username".equals(attributeName)){
                configuration.setUsername(attributeValue);
            }else if("password".equals(attributeName)){
                configuration.setPassword(attributeValue);
            }else{
                continue;
            }
        }
        return  configuration;
    }


    private static Configuration loadMapperXML(Element rootElement, Configuration configuration) throws DocumentException, ClassNotFoundException {
        List<Element> mappers = rootElement.element("mappers").elements("mapper");
        SAXReader reader = new SAXReader();
        for(Element mapperElement : mappers){

            Map<String, Mapper> mapperMap = new HashMap<String, Mapper>();

            Attribute resourceAttribute = mapperElement.attribute("resource");
            /**
             * 判断resource属性是否存在
             * 如果存在则为xml配置
             * 否则为注解配置
             */
            if(null != resourceAttribute){
                System.out.println("使用xml配置");
                String mapperPath = resourceAttribute.getValue();
                InputStream inputStream = XmlUtils.class.getClassLoader().getResourceAsStream(mapperPath);

                Element mapperRootElement = reader.read(inputStream).getRootElement();

                //获取namespace 属性
                String namespace = mapperRootElement.attributeValue("namespace");

                //获取<mapper>标签下面的<select><insert><update>等标签 此处只取select标签
                List<Element> elements = mapperRootElement.elements();

                for(Element element : elements){
                    Mapper mapper = new Mapper();
                    /**
                     * 获取mapper标签中的id、resultType属性和待执行的sql
                     */
                    String id = element.attributeValue("id");
                    String resultType = element.attributeValue("resultType");
                    String querySql = element.getStringValue();
                    mapper.setQuerySql(querySql);
                    mapper.setResultClassName(resultType);
                    /**
                     * 将当前的mapper放入map中其中key为
                     * namespace属性值 + . + mapper标签的id
                     */
                    String key = namespace + "." + id;
                    mapperMap.put(key, mapper);
                }
            }else{
                System.out.println("使用注解配置");
                String classAttributeValue = mapperElement.attributeValue("class");

                Class<?> aClass = Class.forName(classAttributeValue);
                Method[] methods = aClass.getMethods();
                for(Method method : methods){
                    //判断是否存在Select注解
                    boolean annotationPresent = method.isAnnotationPresent(Select.class);
                    if(annotationPresent){
                        Mapper mapper = new Mapper();
                        //取出Select注解
                        Select annotation = method.getAnnotation(Select.class);
                        //取出查询的sql
                        String querySql = annotation.value();
                        //给Mapper赋值
                        mapper.setQuerySql(querySql);
                        //获取当前方法的返回值，还要求必须带有泛型信息
                        Type genericReturnType = method.getGenericReturnType();
                        // 判断是否是泛型
                        if(genericReturnType instanceof ParameterizedType){
                            //如果是则强转
                            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                            //得到泛型类型中的实际参数类型 如返回参数为List<User> 则获取的是User类型
                            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                            //取出第一个
                            Class domainClass = (Class)actualTypeArguments[0];
                            //获取domainClass的全限定类名
                            String resultClassName = domainClass.getName();
                            //给Mapper赋值
                            mapper.setResultClassName(resultClassName);

                        }
                        //组装key的信息
                        //获取方法名
                        String methodName = method.getName();
                        //取出类的全限定类名， 相当于xml配置中的namespace
                        String className = method.getDeclaringClass().getName();
                        String key = className + "." + methodName;
                        //给mapper赋值
                        mapperMap.put(key, mapper);
                    }

                }

            }
            /**
             * 注意这里 setMappers方法其实调用的map.putAll()方法
             */
            configuration.setMappers(mapperMap);
        }
        return configuration;
    }
}
